/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "core_main.h"
#include "../client/client.h"

typedef struct {
	bool			down;			// True if currently down
	int				repeats;		// If > 1, it is auto-repeating
	const char *	binding;		// Bound command text
} key_t;

typedef struct {
	const char *	name;
	int				keyNum;
} keyName_t;

static int			key_catchers;

static bool			key_editMode;

static key_t		key_keys[256];

static keyName_t	key_names[] = {
	{"TAB",				K_TAB},
	{"ENTER",			K_ENTER},
	{"ESCAPE",			K_ESCAPE},
	{"SPACE",			K_SPACE},
	{"BACKSPACE",		K_BACKSPACE},

	{"UPARROW",			K_UPARROW},
	{"DOWNARROW",		K_DOWNARROW},
	{"LEFTARROW",		K_LEFTARROW},
	{"RIGHTARROW",		K_RIGHTARROW},

	{"INS",				K_INS},
	{"DEL",				K_DEL},
	{"HOME",			K_HOME},
	{"END",				K_END},
	{"PGUP",			K_PGUP},
	{"PGDN",			K_PGDN},

	{"LALT",			K_LALT},
	{"RALT",			K_RALT},
	{"LCTRL",			K_LCTRL},
	{"RCTRL",			K_RCTRL},
	{"LSHIFT",			K_LSHIFT},
	{"RSHIFT",			K_RSHIFT},

	{"F1",				K_F1},
	{"F2",				K_F2},
	{"F3",				K_F3},
	{"F4",				K_F4},
	{"F5",				K_F5},
	{"F6",				K_F6},
	{"F7",				K_F7},
	{"F8",				K_F8},
	{"F9",				K_F9},
	{"F10",				K_F10},
	{"F11",				K_F11},
	{"F12",				K_F12},

	{"CAPSLOCK",		K_CAPSLOCK},
	{"SCROLLLOCK",		K_SCROLLLOCK},
	{"PAUSE",			K_PAUSE},

	{"APPS",			K_APPS},
	{"LWIN",			K_LWIN},
	{"RWIN",			K_RWIN},

	{"KP_HOME",			K_KP_HOME},
	{"KP_UPARROW",		K_KP_UPARROW},
	{"KP_PGUP",			K_KP_PGUP},
	{"KP_LEFTARROW",	K_KP_LEFTARROW},
	{"KP_5",			K_KP_5},
	{"KP_RIGHTARROW",	K_KP_RIGHTARROW},
	{"KP_END",			K_KP_END},
	{"KP_DOWNARROW",	K_KP_DOWNARROW},
	{"KP_PGDN",			K_KP_PGDN},
	{"KP_INS",			K_KP_INS},
	{"KP_DEL",			K_KP_DEL},
	{"KP_SLASH",		K_KP_SLASH},
	{"KP_STAR",			K_KP_STAR},
	{"KP_MINUS",		K_KP_MINUS},
	{"KP_PLUS",			K_KP_PLUS},
	{"KP_ENTER",		K_KP_ENTER},
	{"KP_NUMLOCK",		K_KP_NUMLOCK},

	{"MOUSE1",			K_MOUSE1},
	{"MOUSE2",			K_MOUSE2},
	{"MOUSE3",			K_MOUSE3},
	{"MOUSE4",			K_MOUSE4},
	{"MOUSE5",			K_MOUSE5},

	{"MWHEELUP",		K_MWHEELUP},
	{"MWHEELDOWN",		K_MWHEELDOWN},

	{"SEMICOLON",		';'},				// A raw semicolon separates commands
	{"APOSTROPHE",		'\''},				// A raw apostrophe separates literals

	{NULL,				0}
};


/*
 ==================
 Key_StringToKeyNum
 ==================
*/
int Key_StringToKeyNum (const char *string){

	keyName_t	*kn;

	if (!string || !string[0])
		return -1;

	if (!string[1])
		return string[0];

	// Scan for a key name match
	for (kn = key_names; kn->name; kn++){
		if (!Str_ICompare(kn->name, string))
			return kn->keyNum;
	}

	return -1;
}

/*
 ==================
 Key_KeyNumToString
 ==================
*/
const char *Key_KeyNumToString (int keyNum){

	static char	string[2];
	keyName_t	*kn;

	if (keyNum == -1)
		return "<KEY NOT FOUND>";

	if (keyNum < 0 || keyNum > 255)
		return "<OUT OF RANGE>";

	// Check for printable ASCII
	if (keyNum > 32 && keyNum < 127 && keyNum != ';' && keyNum != '\''){
		string[0] = keyNum;
		string[1] = 0;

		return string;
	}

	// Scan for key number match
	for (kn = key_names; kn->name; kn++){
		if (kn->keyNum == keyNum)
			return kn->name;
	}

	return "<UNKNOWN KEY>";
}

/*
 ==================
 Key_GetBinding
 ==================
*/
const char *Key_GetBinding (int keyNum){

	key_t	*k;

	if (keyNum < 0 || keyNum > 255)
		return "";

	k = &key_keys[keyNum];

	if (!k->binding)
		return "";

	return k->binding;
}

/*
 ==================
 Key_SetBinding
 ==================
*/
void Key_SetBinding (int keyNum, const char *binding){

	key_t	*k;

	if (keyNum < 0 || keyNum > 255)
		return;

	k = &key_keys[keyNum];

	// Consider this like modifying an archive variable, so the config file
	// will be written the next frame
	CVar_SetModifiedFlags(CVAR_ARCHIVE);

	// Free old binding
	if (k->binding){
		Mem_Free(k->binding);
		k->binding = NULL;
	}

	// Allocate memory for new binding
	if (!binding || !binding[0])
		return;

	k->binding = Mem_CopyString(binding, TAG_COMMON);
}

/*
 ==================
 Key_WriteBindings
 ==================
*/
void Key_WriteBindings (fileHandle_t f){

	key_t	*k;
	int		i;

	FS_Printf(f, "unbindAll" LINE_SEPARATOR);

	for (i = 0, k = key_keys; i < 256; i++, k++){
		if (!k->binding)
			continue;

		FS_Printf(f, "bind %s \"%s\"" LINE_SEPARATOR, Key_KeyNumToString(i), k->binding);
	}
}

/*
 ==================
 Key_GetCatchers
 ==================
*/
int Key_GetCatchers (void){

	return key_catchers;
}

/*
 ==================
 Key_SetCatchers
 ==================
*/
void Key_SetCatchers (int catchers){

	key_catchers |= catchers;
}

/*
 ==================
 Key_RemoveCatchers
 ==================
*/
void Key_RemoveCatchers (int catchers){

	key_catchers &= ~catchers;
}

/*
 ==================
 Key_SetEditMode
 ==================
*/
void Key_SetEditMode (bool editMode){

	key_editMode = editMode;
}

/*
 ==================
 Key_IsDown
 ==================
*/
bool Key_IsDown (int key){

	if (key < 0 || key > 255)
		return false;

	return key_keys[key].down;
}

/*
 ==================
 Key_ClearStates
 ==================
*/
void Key_ClearStates (void){

	key_t	*k;
	int		i;

	for (i = 0, k = key_keys; i < 256; i++, k++){
		if (k->down)
			Key_Event(i, false, 0);

		k->down = false;
		k->repeats = 0;
	}
}

/*
 ==================
 Key_Event
 
 Called by the system for both key down and key up events
 ==================
*/
void Key_Event (int key, bool down, int time){

	char	command[MAX_STRING_LENGTH];
	key_t	*k;

	if (key < 0 || key > 255)
		return;

	k = &key_keys[key];

	// Update key status
	k->down = down;

	if (down)
		k->repeats++;
	else
		k->repeats = 0;

	// Console key is hardcoded, so the user can never unbind it
	if (key == '`' || key == '~'){
		if (!down)
			return;
		Con_ToggleConsole_f();
		return;
	}

	// Escape key is hardcoded, so the user can never unbind it
	if (key == K_ESCAPE){
		if (!down)
			return;

		// Send the key down event to the appropriate handler
		if (key_catchers & KEYCATCH_CONSOLE){
			Con_KeyDownEvent(key);
			return;
		}

		if (key_catchers & KEYCATCH_GUI){
			GUI_KeyDownEvent(key);
			return;
		}

		if (cls.state != CS_ACTIVE){
			// If connecting or loading a level, disconnect
			if (cls.state != CS_DISCONNECTED)
				Com_Error(false, "Disconnected from server");

			// Bring up the main menu GUI
			GUI_Activate(GUI_MAIN);
			return;
		}
		// Bring up the in-game menu GUI
		GUI_Activate(GUI_INGAME);
		return;
	}

	// Key up events only generate commands if the game key binding is a button
	// command (leading + sign). These will be processed even in console or GUI
	// mode, to keep the character from continuing an action started before a
	// mode switch.
	if (!down){
		if (!k->binding)
			return;

		if (k->binding[0] == '+'){
			Str_SPrintf(command, sizeof(command), "-%s %i %i\n", &k->binding[1], key, time);
			Cmd_AppendText(command);
		}

		return;
	}

	// Send the key down event to the appropriate handler
	if (key_catchers & KEYCATCH_CONSOLE){
		Con_KeyDownEvent(key);
		return;
	}

	if (key_catchers & KEYCATCH_GUI){
		GUI_KeyDownEvent(key);
		return;
	}

	if (cls.state != CS_ACTIVE){
		Con_KeyDownEvent(key);
		return;
	}

	// If an editor is active, send mouse events to it
	if (key_editMode && key == K_MOUSE1){
		if (ED_MouseEvent())
			return;		// Don't generate a game command
	}

	// Generate a game command
	if (!k->binding){
		if (k->repeats == 100)
			Com_Printf("'%s' is unbound, use controls menu to set\n", Key_KeyNumToString(key));

		return;
	}

	if (k->repeats > 1)
		return;		// Ignore auto-repeats in game

	if (k->binding[0] == '+')
		Str_SPrintf(command, sizeof(command), "%s %i %i\n", k->binding, key, time);
	else
		Str_SPrintf(command, sizeof(command), "%s\n", k->binding);

	Cmd_AppendText(command);
}

/*
 ==================
 Key_CharEvent

 Called by the system for char events
 ==================
*/
void Key_CharEvent (int c){

	if (c < 0 || c > 255)
		return;

	// The console key should never be used as a char
	if (c == '`' || c == '~')
		return;

	// Send the char event to the appropriate handler
	if (key_catchers & KEYCATCH_CONSOLE){
		Con_CharEvent(c);
		return;
	}

	if (key_catchers & KEYCATCH_GUI){
		GUI_CharEvent(c);
		return;
	}

	if (cls.state != CS_ACTIVE){
		Con_CharEvent(c);
		return;
	}
}

/*
 ==================
 Key_Bind_f
 ==================
*/
static void Key_Bind_f (void){

	int		keyNum;

	if (Cmd_Argc() < 2){
		Com_Printf("Usage: bind <key> [command]\n");
		return;
	}

	keyNum = Key_StringToKeyNum(Cmd_Argv(1));
	if (keyNum == -1){
		Com_Printf("\"%s\" isn't a valid key\n", Cmd_Argv(1));
		return;
	}

	if (Cmd_Argc() == 2){
		if (key_keys[keyNum].binding)
			Com_Printf("\"%s\" = \"%s\"\n", Cmd_Argv(1), key_keys[keyNum].binding);
		else
			Com_Printf("\"%s\" is not bound\n", Cmd_Argv(1));

		return;
	}

	Key_SetBinding(keyNum, Cmd_Args(2, -1, true));
}

/*
 ==================
 Key_Unbind_f
 ==================
*/
static void Key_Unbind_f (void){

	int		keyNum;

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: unbind <key>\n");
		return;
	}

	keyNum = Key_StringToKeyNum(Cmd_Argv(1));
	if (keyNum == -1){
		Com_Printf("\"%s\" isn't a valid key\n", Cmd_Argv(1));
		return;
	}

	if (!key_keys[keyNum].binding){
		Com_Printf("\"%s\" is not bound\n", Cmd_Argv(1));
		return;
	}

	Key_SetBinding(keyNum, NULL);
}

/*
 ==================
 Key_UnbindAll_f
 ==================
*/
static void Key_UnbindAll_f (void){

	key_t	*k;
	int		i;

	for (i = 0, k = key_keys; i < 256; i++, k++){
		if (!k->binding)
			continue;

		Key_SetBinding(i, NULL);
	}
}

/*
 ==================
 Key_ListBinds_f
 ==================
*/
static void Key_ListBinds_f (void){

	key_t	*k;
	int		i;

	for (i = 0, k = key_keys; i < 256; i++, k++){
		if (!k->binding)
			continue;

		Com_Printf("    %-20s \"%s\"\n", Key_KeyNumToString(i), k->binding);
	}
}

/*
 ==================
 Key_Init
 ==================
*/
void Key_Init (void){

	Cmd_AddCommand("bind", Key_Bind_f, "Binds a command to a key");
	Cmd_AddCommand("unbind", Key_Unbind_f, "Unbinds any command from a key");
	Cmd_AddCommand("unbindAll", Key_UnbindAll_f, "Unbinds any commands from all keys");
	Cmd_AddCommand("listBinds", Key_ListBinds_f, "Lists key bindings");
}

/*
 ==================
 Key_Shutdown
 ==================
*/
void Key_Shutdown (void){

	key_t	*k;
	int		i;

	Cmd_RemoveCommand("bind");
	Cmd_RemoveCommand("unbind");
	Cmd_RemoveCommand("unbindAll");
	Cmd_RemoveCommand("listBinds");

	key_editMode = false;

	for (i = 0, k = key_keys; i < 256; i++, k++){
		if (k->binding)
			Mem_Free(k->binding);
	}

	memset(key_keys, 0, sizeof(key_keys));
}
